﻿//-----------------------------------------------------------------------
// <copyright file="MainWindowViewModel.cs" company="AFS">
// Copyright (c) AFS. All rights reserved.
//
//    This file is part of Chatbox.
//
//    Chatbox is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    any later version.
//
//    Chatbox is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//    GNU Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public License
//    along with Chatbox. If not, see http://www.gnu.org/copyleft/lesser.html
// </copyright>
//----------------------------------------------------------------------- 

namespace ChatBox.UI.Windows.Main
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Web;
    using System.Windows;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Threading;

    using ChatBox.DomainObjects.ResultData;
    using ChatBox.DomainObjects.Settings;
    using ChatBox.DomainObjects.XmlFeedObjects;
    using ChatBox.Interfaces;
    using ChatBox.UI.Controls;
    using ChatBox.UI.Media;
    using ChatBox.UI.MessageDisplay;
    using ChatBox.UI.Themes;
    using ChatBox.UI.ViewModels;
    using ChatBox.UI.Windows.About;
    using ChatBox.UI.Windows.NewMessagePopup;
    using ChatBox.UI.Windows.NewVersion;
    using ChatBox.UI.Windows.PrivateMessages;
    using ChatBox.UI.Windows.SendPrivateMessage;
    using ChatBox.UI.Windows.Settings;
    using ChatBox.UI.Windows.Tickers;
    using ChatBox.UI.Windows.UserNamePassword;
    using ChatBox.Utility;
    using ChatBox.XamlUtility;

    /// <summary>
    /// View Model for the main form
    /// </summary>
    public class MainWindowViewModel : WindowViewModel, IDisposable
    {
        #region data

        /// <summary>
        /// View model for sending text
        /// </summary>
        private readonly SpeakViewModel speak;

        /// <summary>
        ///  source of Ui settings data
        /// </summary>
        private readonly IUISettingsRepository uiSettingsRepository;

        /// <summary>
        ///  source of server settings data
        /// </summary>
        private readonly IServerSettingsRepository serverSettingsRepository;

        /// <summary>
        /// UI theme manager
        /// </summary>
        private readonly ThemeManager themeManager;

        /// <summary>
        /// The lines to show in status
        /// </summary>
        private readonly ObservableCollection<StatusLineViewModel> statusLines = new ObservableCollection<StatusLineViewModel>();

        /// <summary>
        /// Private messages shown inline
        /// </summary>
        private readonly List<Message> inlinePrivateMessages = new List<Message>();

        /// <summary>
        /// The chat Engine interface 
        /// </summary>
        private IChatEngine chatEngine;

        /// <summary>
        /// Visibility of the topic area for binding
        /// </summary>
        private VisibilityAnimationState topicVisibilityState;

        /// <summary>
        /// Visibility of the topic area for binding
        /// </summary>
        private Visibility topicVisibility;

        /// <summary>
        /// Is the topic visible
        /// </summary>
        private bool isTopicVisible;

        /// <summary>
        /// Visibility of the throbber for binding
        /// </summary>
        private VisibilityAnimationState throbberVisibilityState;

        /// <summary>
        /// Visibility of the throbber for binding
        /// </summary>
        private Visibility throbberVisibility;

        /// <summary>
        /// Is the throbber visible
        /// </summary>
        private bool isThrobberVisible;

        /// <summary>
        /// Visibility of the other users for binding
        /// </summary>
        private VisibilityAnimationState otherUsersVisibilityState;

        /// <summary>
        /// Visibility of the other users for binding
        /// </summary>
        private Visibility otherUsersVisibility;

        /// <summary>
        /// Is the other users visible
        /// </summary>
        private bool isOtherUsersVisible;

        /// <summary>
        /// is the user logged on
        /// </summary>
        private bool isLoggedOn;

        /// <summary>
        /// The window status text
        /// </summary>
        private string statusText;

        /// <summary>
        /// The text in the status link
        /// </summary>
        private string statusLinkText;

        /// <summary>
        /// font size for the status 
        /// </summary>
        private double tooltipFontSize;

        /// <summary>
        /// The document to show the messages
        /// </summary>
        private FlowDocument messagesDocument;

        /// <summary>
        /// The document to show the topic
        /// </summary>
        private FlowDocument topicDocument;

        /// <summary>
        /// loading text visiblity for binding
        /// </summary>
        private Visibility loadingTextVisibility = Visibility.Visible;

        /// <summary>
        /// Player for new message sound
        /// </summary>
        private NotificationSoundPlayer notificationSoundPlayer = new NotificationSoundPlayer();

        /// <summary>
        /// Track whether Dispose has been called.
        /// </summary>
        private bool disposed;

        #endregion

        /// <summary>
        /// Initializes a new instance of the MainWindowViewModel class 
        /// </summary>
        /// <param name="chatEngine">the chat engine to use</param>
        /// <param name="uiSettingsRepository">the source of UI settings</param>
        /// <param name="serverSettingsRepository">the source of server settings</param>
        /// <param name="themeManager">the theme manager to use</param>
        /// <param name="speakViewModel">the view model for speaking</param>
        /// <param name="logOnCommand">Command to do log on</param>
        /// <param name="clearLogOnCommand">Command to clear the log on</param>
        /// <param name="speakCommand">Command to send a message</param>
        /// <param name="helpAboutCommand">command to show the help about window</param>
        /// <param name="helpDocumentationCommand">the command for showing help Documentation</param>
        /// <param name="showPrivateMessagesCommand">the command to show private messages</param>
        /// <param name="showTickersCommand">the command to show tickers</param>
        /// <param name="showNewMessagePopupCommand">Command to show a new message popup</param>
        /// <param name="showUserNamePasswordPromptCommand">command to show User name and password popup</param>
        /// <param name="shutdownCommand">the command to shut down the view model</param>
        /// <param name="showSendPrivateMessageCommand">command to show the "send private message" window</param>
        /// <param name="showSettingsWindowCommand">command to show the settings window</param>
        /// <param name="mainWindowReadSettingsCommand">command to read settings into the view model</param>
        /// <param name="showNewVersionCommand">command to show the new version dialog</param>
        /// <param name="refreshSettingsCommand">command to refrehs the Ui settings</param>
        public MainWindowViewModel(
            IChatEngine chatEngine, 
            IUISettingsRepository uiSettingsRepository,
            IServerSettingsRepository serverSettingsRepository,
            ThemeManager themeManager,
            SpeakViewModel speakViewModel,
            LogOnCommand logOnCommand,
            ClearLogOnCommand clearLogOnCommand,
            SpeakCommand speakCommand,
            ShowAboutWindowCommand helpAboutCommand,
            HelpDocumentationCommand helpDocumentationCommand,
            ShowPrivateMessagesCommand showPrivateMessagesCommand,
            ShowTickersCommand showTickersCommand,
            ShowNewMessagePopupCommand showNewMessagePopupCommand,
            ShowUserNamePasswordPromptCommand showUserNamePasswordPromptCommand,
            MainWindowShutdownCommand shutdownCommand,
            ShowSendPrivateMessageCommand showSendPrivateMessageCommand,
            ShowSettingsWindowCommand showSettingsWindowCommand,
            MainWindowReadSettingsCommand mainWindowReadSettingsCommand,
            ShowNewVersionCommand showNewVersionCommand,
            RefreshSettingsCommand refreshSettingsCommand)
        {
            this.AttachChatEngine(chatEngine);
            this.uiSettingsRepository = uiSettingsRepository;
            this.serverSettingsRepository = serverSettingsRepository;
            
            this.themeManager = themeManager;
            this.speak = speakViewModel;

            this.LogOnCommand = logOnCommand;
            this.ClearLogOnCommand = clearLogOnCommand;
            this.SpeakCommand = speakCommand;
            this.HelpAbout = helpAboutCommand;
            this.HelpDocumentationCommand = helpDocumentationCommand;
            this.ShowPrivateMessagesCommand = showPrivateMessagesCommand;
            this.ShowTickersCommand = showTickersCommand;
            this.ShowNewMessagePopupCommand = showNewMessagePopupCommand;
            this.ShowUserNamePasswordPromptCommand = showUserNamePasswordPromptCommand;
            this.ShutdownCommand = shutdownCommand;
            this.ShowSendPrivateMessageCommand = showSendPrivateMessageCommand;
            this.ShowSettingsWindowCommand = showSettingsWindowCommand;
            this.MainWindowReadSettingsCommand = mainWindowReadSettingsCommand;
            this.ShowNewVersionCommand = showNewVersionCommand;
            this.RefreshSettingsCommand = refreshSettingsCommand;

            // can't IoC this one, it needs a back reference to this view model
            this.OperationCompletedCommand = new OperationCompletedCommand(this, uiSettingsRepository);
        }

        /// <summary>
        /// Finalizes an instance of the MainWindowViewModel class
        /// </summary>
        ~MainWindowViewModel()
        {
            this.Dispose(false);
        }

        /// <summary>
        /// Gets the chat engine
        /// </summary>
        public IChatEngine ChatEngine
        {
            get { return this.chatEngine; }
        }

        #region commands

        /// <summary>
        /// Gets the command to log on
        /// </summary>
        public ICommand LogOnCommand { get; private set; }

        /// <summary>
        /// Gets the command to clear the log on
        /// </summary>
        public ICommand ClearLogOnCommand { get; private set; }

        /// <summary>
        /// Gets the command to speak
        /// </summary>
        public ICommand SpeakCommand { get; private set; }

        /// <summary>
        /// Gets the help about command
        /// </summary>
        public ICommand HelpAbout { get; private set; }
 
        /// <summary>
        /// Gets the command for help documentation
        /// </summary>
        public ICommand HelpDocumentationCommand { get; private set; }

        /// <summary>
        /// Gets the command to show private messages
        /// </summary>
        public ICommand ShowPrivateMessagesCommand { get; private set; }

        /// <summary>
        /// Gets the command to show tickers
        /// </summary>
        public ICommand ShowTickersCommand { get; private set; }

        /// <summary>
        /// Gets the command to show the new message popup window
        /// </summary>
        public ICommand ShowNewMessagePopupCommand { get; private set; }

        /// <summary>
        /// Gets the command to show the user name and password prompt
        /// </summary>
        public ICommand ShowUserNamePasswordPromptCommand { get; private set; }

        /// <summary>
        /// Gets the command to shut down the view model
        /// </summary>
        public ICommand ShutdownCommand { get; private set; }

        /// <summary>
        /// Gets the command to show the "Send Private Message" window
        /// </summary>
        public ICommand ShowSendPrivateMessageCommand { get; private set; }

        /// <summary>
        /// Gets the command to show the Settings window
        /// </summary>
        public ICommand ShowSettingsWindowCommand { get; private set; }

        /// <summary>
        /// Gets the command to read settings into this view model
        /// </summary>
        public ICommand MainWindowReadSettingsCommand { get; private set; }

        /// <summary>
        /// Gets the command to process operation completed data
        /// </summary>
        public ICommand OperationCompletedCommand { get; private set; }

        /// <summary>
        /// Gets the command to show the "new version" dialog
        /// </summary>
        public ICommand ShowNewVersionCommand { get; private set; }

        /// <summary>
        /// Gets the command to refresh the settings
        /// </summary>
        public ICommand RefreshSettingsCommand { get; private set; }

        #endregion

        #region properties with notify

        /// <summary>
        /// Gets or sets the visiblity of the topic area
        /// </summary>
        public VisibilityAnimationState TopicVisibilityState
        {
            get
            {
                return this.topicVisibilityState;
            }

            set
            {
                if (this.topicVisibilityState != value)
                {
                    this.topicVisibilityState = value;
                    OnPropertyChanged("TopicVisibilityState");

                    this.TopicVisibility = value.ToVisibility();
                    this.IsTopicVisible = value.IsVisibleState();
                }
            }
        }
        
        /// <summary>
        /// Gets or sets the visibility state for the topic
        /// </summary>
        public Visibility TopicVisibility
        {
            get
            {
                return this.topicVisibility;
            }

            set
            {
                if (this.topicVisibility != value)
                {
                    this.topicVisibility = value;
                    OnPropertyChanged("TopicVisibility");
                }
            }
        }

        /// <summary>
        /// Gets or sets the visibility state for the topic
        /// </summary>
        public Visibility LoadingTextVisibility
        {
            get
            {
                return this.loadingTextVisibility;
            }

            set
            {
                if (this.loadingTextVisibility != value)
                {
                    this.loadingTextVisibility = value;
                    OnPropertyChanged("LoadingTextVisibility");
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the topic is visible
        /// </summary>
        public bool IsTopicVisible
        {
            get
            {
                return this.isTopicVisible;
            }

            set
            {
                if (this.isTopicVisible != value)
                {
                    this.isTopicVisible = value;
                    OnPropertyChanged("IsTopicVisible");

                    if (value)
                    {
                        if (!this.TopicVisibilityState.IsVisibleState())
                        {
                            this.TopicVisibilityState = VisibilityAnimationState.Showing;
                        }
                    }
                    else
                    {
                        if (this.TopicVisibilityState.IsVisibleState())
                        {
                            this.TopicVisibilityState = VisibilityAnimationState.Hiding;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the visiblity of the speak area
        /// </summary>
        public VisibilityAnimationState ThrobberVisibilityState
        {
            get
            {
                return this.throbberVisibilityState;
            }

            set
            {
                if (this.throbberVisibilityState != value)
                {
                    this.throbberVisibilityState = value;
                    OnPropertyChanged("ThrobberVisibilityState");

                    this.ThrobberVisibility = value.ToVisibility();
                    this.IsThrobberVisible = value.IsVisibleState();
                }
            }
        }

        /// <summary>
        /// Gets or sets the visibility state for the throbber
        /// </summary>
        public Visibility ThrobberVisibility
        {
            get
            {
                return this.throbberVisibility;
            }

            set
            {
                if (this.throbberVisibility != value)
                {
                    this.throbberVisibility = value;
                    OnPropertyChanged("ThrobberVisibility");
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the throbber is visible
        /// </summary>
        public bool IsThrobberVisible
        {
            get
            {
                return this.isThrobberVisible;
            }

            set
            {
                if (this.isThrobberVisible != value)
                {
                    this.isThrobberVisible = value;
                    OnPropertyChanged("IsThrobberVisible");

                    if (value)
                    {
                        if (!this.ThrobberVisibilityState.IsVisibleState())
                        {
                            this.ThrobberVisibilityState = VisibilityAnimationState.Showing;
                        }
                    }
                    else
                    {
                        if (this.ThrobberVisibilityState.IsVisibleState())
                        {
                            this.ThrobberVisibilityState = VisibilityAnimationState.Hiding;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the visiblity of the other users
        /// </summary>
        public VisibilityAnimationState OtherUsersVisibilityState
        {
            get
            {
                return this.otherUsersVisibilityState;
            }

            set
            {
                if (this.otherUsersVisibilityState != value)
                {
                    this.otherUsersVisibilityState = value;
                    OnPropertyChanged("OtherUsersVisibilityState");

                    this.OtherUsersVisibility = value.ToVisibility();
                    this.IsOtherUsersVisible = value.IsVisibleState();
                }
            }
        }

        /// <summary>
        /// Gets or sets the visibility state for the other users
        /// </summary>
        public Visibility OtherUsersVisibility
        {
            get
            {
                return this.otherUsersVisibility;
            }

            set
            {
                if (this.otherUsersVisibility != value)
                {
                    this.otherUsersVisibility = value;
                    OnPropertyChanged("OtherUsersVisibility");
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the other users are visible
        /// </summary>
        public bool IsOtherUsersVisible
        {
            get
            {
                return this.isOtherUsersVisible;
            }

            set
            {
                if (this.isOtherUsersVisible != value)
                {
                    this.isOtherUsersVisible = value;
                    OnPropertyChanged("IsOtherUsersVisible");

                    if (value)
                    {
                        if (!this.OtherUsersVisibilityState.IsVisibleState())
                        {
                            this.OtherUsersVisibilityState = VisibilityAnimationState.Showing;
                        }
                    }
                    else
                    {
                        if (this.OtherUsersVisibilityState.IsVisibleState())
                        {
                            this.OtherUsersVisibilityState = VisibilityAnimationState.Hiding;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the user is logged on
        /// </summary>
        public bool IsLoggedOn
        {
            get
            {
                return this.isLoggedOn;
            }

            set
            {
                if (this.isLoggedOn != value)
                {
                    this.isLoggedOn = value;
                    OnPropertyChanged("IsLoggedOn");
                }
            }
        }

        /// <summary>
        /// Gets or sets the window status text
        /// </summary>
        public string StatusText
        {
            get
            {
                return this.statusText;
            }

            set
            {
                if (this.statusText != value)
                {
                    this.statusText = value;
                    OnPropertyChanged("StatusText");
                }
            }
        }

        /// <summary>
        /// Gets or sets the text in the status link
        /// </summary>
        public string StatusLinkText
        {
            get
            {
                return this.statusLinkText;
            }

            set
            {
                if (this.statusLinkText != value)
                {
                    this.statusLinkText = value;
                    OnPropertyChanged("StatusLinkText");
                }
            }
        }

        /// <summary>
        /// Gets or sets the font size for tooltips
        /// </summary>
        public double TooltipFontSize
        {
            get
            {
                return this.tooltipFontSize;
            }

            set
            {
                if (this.tooltipFontSize != value)
                {
                    this.tooltipFontSize = value;
                    OnPropertyChanged("TooltipFontSize");
                }
            }
        }

        /// <summary>
        /// Gets or sets the FlowDocument to show the messages
        /// </summary>
        public FlowDocument MessagesDocument
        {
            get
            {
                return this.messagesDocument;
            }

            set
            {
                if (this.messagesDocument != value)
                {
                    this.messagesDocument = value;
                    OnPropertyChanged("MessagesDocument");
                }
            }
        }

        /// <summary>
        /// Gets or sets the FlowDocument to show the topic
        /// </summary>
        public FlowDocument TopicDocument
        {
            get
            {
                return this.topicDocument;
            }

            set
            {
                if (this.topicDocument != value)
                {
                    this.topicDocument = value;
                    OnPropertyChanged("TopicDocument");
                }
            }
        }

        /// <summary>
        /// Gets the lines to show in connection status tooltip
        /// </summary>
        public ObservableCollection<StatusLineViewModel> StatusLines
        {
            get
            {
                return this.statusLines;
            }
        }

        #endregion

        #region other properties

        /// <summary>
        /// Gets or sets the last state of the connection 
        /// </summary>
        public ConnectionState ConnectionState { get; set; }

        /// <summary>
        /// Gets or sets the text that starts internal links
        /// </summary>
        public string LinkTitlePrefix { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the initial load has happened yet
        /// </summary>
        public bool HasFirstPublicMessages { get; set; }

        /// <summary>
        /// Gets or sets format options for public messages
        /// </summary>
        public FormatOptions PublicMessageFormatOptions { get; set; }

        /// <summary>
        /// Gets or sets format options for topic
        /// </summary>
        public FormatOptions TopicFormatOptions { get; set; }

        /// <summary>
        /// Gets or sets the formatter for messages into a document
        /// </summary>
        public MessageFormatter MessageFormatter { get; set; }

        /// <summary>
        /// Gets or sets the formatter for the topic into a document
        /// </summary>
        public TopicFormatter TopicFormatter { get; set; }

        /// <summary>
        /// Gets or sets the context for hyperlinks
        /// </summary>
        public HyperlinkContext HyperlinkContext { get; set; }

        /// <summary>
        /// Gets or sets the interface to scrolling the main window to bottom
        /// </summary>
        public DocumentViewerScrollToBottom ScrollToBottom { get; set; }

        /// <summary>
        /// Gets or sets the throbber display control
        /// </summary>
        public Throbber Throbber { get; set; }

        /// <summary>
        /// Gets or sets the other users display control
        /// </summary>
        public OtherUsersDisplay OtherUsers { get; set; }

        /// <summary>
        /// Gets the UI theme manager
        /// </summary>
        public ThemeManager ThemeManager
        {
            get { return this.themeManager; }
        }

        /// <summary>
        ///  Gets the source of Ui settings data
        /// </summary>
        public IUISettingsRepository UISettingsRepository
        {
            get { return this.uiSettingsRepository; }
        }

        /// <summary>
        ///  Gets the source of server settings data
        /// </summary>
        public IServerSettingsRepository ServerSettingsRepository
        {
            get { return this.serverSettingsRepository; }
        }

        /// <summary>
        /// Gets the view model for sending text
        /// </summary>
        public SpeakViewModel Speak
        {
            get { return this.speak; }
        }

        /// <summary>
        /// Gets or sets the window to show Private messages
        /// </summary>
        public PrivateMessagesWindow PrivateMessagesWindow { get; set; }

        /// <summary>
        /// Gets or sets the window to show tickers
        /// </summary>
        public TickersWindow TickersWindow { get; set; }

        /// <summary>
        /// Gets the player for new message sound
        /// </summary>
        public NotificationSoundPlayer NotificationSoundPlayer
        {
            get { return this.notificationSoundPlayer; }
        }

        /// <summary>
        /// Gets private messages shown inline
        /// </summary>
        public List<Message> InlinePrivateMessages
        {
            get { return this.inlinePrivateMessages; }
        }

        /// <summary>
        /// Gets or sets the style for the send message button
        /// </summary>
        public Style SendMessageButtonStyle { get; set; }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Implements the IDisposable pattern
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region methods

        /// <summary>
        /// Read the engine logged on state
        /// </summary>
        public void ReadEngineLogOn()
        {
            this.IsLoggedOn = (this.chatEngine != null) && this.chatEngine.IsLoggedOn;
        }

        /// <summary>
        /// Write values from the model to the UI settings
        /// </summary>
        public void WriteSettings()
        {
            UISettingsData uiSettings = this.uiSettingsRepository.ReadSettings();

            uiSettings.ShowSpeak = this.speak.SpeakVisibilityState.IsVisibleState();
            uiSettings.ShowTopic = this.IsTopicVisible;
            uiSettings.ShowThrobber = this.IsThrobberVisible;
            uiSettings.ShowOtherUsers = this.IsOtherUsersVisible;

            this.uiSettingsRepository.SaveSettings(uiSettings);
        }

        /// <summary>
        /// redisplay the messages
        /// </summary>
        public void DisplayMessages()
        {
            if (this.ChatEngine == null || this.MessageFormatter == null)
            {
                return;
            }

            this.ScrollToBottom.MarkAtBottom();

            IEnumerable<Message> messagesToShow;

            if (this.InlinePrivateMessages.Count > 0)
            {
                List<Message> allMessages = new List<Message>(this.ChatEngine.PublicChatMessages);
                allMessages.AddRange(this.InlinePrivateMessages);

                messagesToShow = allMessages.OrderBy(m => m.Id);
            }
            else
            {
                messagesToShow = this.ChatEngine.PublicChatMessages;
            }

            this.MessagesDocument = this.MessageFormatter.GenerateMessagesDocument(messagesToShow, this.PublicMessageFormatOptions);

            // if it was scrolled to the bottom, put it back there
            this.ScrollToBottom.ResetAtBottom();

            this.LoadingTextVisibility = Visibility.Collapsed;
        }

        /// <summary>
        /// Redisplay the topic
        /// </summary>        
        public void DisplayTopic()
        {
            // topic contains escaped tags, so de-escape them
            string topic = HttpUtility.HtmlDecode(this.ChatEngine.Topic);

            this.TopicDocument = this.TopicFormatter.GenerateTopicDocument(topic, this.TopicFormatOptions);
        }

        /// <summary>
        /// Show the other users
        /// </summary>
        public void ShowOtherUsers()
        {
            this.OtherUsers.HyperlinkContext = this.HyperlinkContext;
            this.OtherUsers.UISettingsRepository = this.uiSettingsRepository;
            this.OtherUsers.ShowOtherUsers(this.ChatEngine.OtherUsers);
        }

        /// <summary>
        /// Launch a browser window with the Uri
        /// </summary>
        /// <param name="navigateUri">the Uri to show</param>
        public void NavigateToUri(Uri navigateUri)
        {
            try
            {
                Process.Start(new ProcessStartInfo(navigateUri.ToString()));
            }
            catch (Exception ex)
            {
                // firefox is wedged. display the failure
                string message = "Click on link " + navigateUri + " failed";
                this.StatusText = message + ": " + ex.Message;
                this.ChatEngine.Logger.LogException(message, ex);
            }
        }

        /// <summary>
        /// Handler for mouse over a link
        /// </summary>
        /// <param name="link">the link that was clicked on</param>
        public void LinkClick(Hyperlink link)
        {
            if (link == null)
            {
                return;
            }

            // cache the site prefix string
            if (string.IsNullOrEmpty(this.LinkTitlePrefix))
            {
                ServerSettingsData settingsData = this.ServerSettingsRepository.ReadSettings();
                this.LinkTitlePrefix = settingsData.Server + "/title/";
            }

            if (link.NavigateUri != null)
            {
                string url = link.NavigateUri.ToString();
                if (url.StartsWith(this.LinkTitlePrefix, StringComparison.OrdinalIgnoreCase))
                {
                    url = url.TrimPrefix(this.LinkTitlePrefix);

                    // double-decode seems to be necessary. May be because of an error up the line?
                    url = HttpUtility.UrlDecode(url);
                    url = HttpUtility.UrlDecode(url);
                }

                this.StatusLinkText = url;
            }
        }

        /// <summary>
        /// Save the window size and position
        /// </summary>
        public void SaveWindowPosition()
        {
            UISettingsData settingsData = this.uiSettingsRepository.ReadSettings();
            settingsData.MainWindowDimensions = new Rect(this.Window.Left, this.Window.Top, this.Window.Width, this.Window.Height);

            this.uiSettingsRepository.SaveSettings(settingsData);
        }

        /// <summary>
        /// Check that a user name and password are present
        /// </summary>
        /// <param name="actionName">the name of the action in progress</param>
        /// <returns>true if the user name and password are present</returns>
        public bool CheckNameAndPassword(string actionName)
        {
            ServerSettingsData settingsData = this.ServerSettingsRepository.ReadSettings();

            // check that basic setup is in place
            // first try a popup to capture missing user name and password
            if (string.IsNullOrEmpty(settingsData.UserName) || string.IsNullOrEmpty(settingsData.Password))
            {
                this.ShowUserNamePasswordPromptCommand.Execute(this);
                settingsData = this.ServerSettingsRepository.ReadSettings();
            }

            // then just report
            if (string.IsNullOrEmpty(settingsData.UserName))
            {
                this.StatusText = "You must enter your log on name in order to " + actionName;
                return false;
            }

            if (string.IsNullOrEmpty(settingsData.Password))
            {
                this.StatusText = "You must enter your password in order to " + actionName;
                return false;
            }

            return true;
        }

        /// <summary>
        /// Refresh UI font sizes
        /// </summary>
        /// <param name="startup">true when the application is starting up</param>
        public void RefreshFontSizes(bool startup)
        {
            this.MainWindowReadSettingsCommand.Execute(this);

            // is there data to redisplay?
            if (!startup)
            {
                this.DisplayMessages();
                this.DisplayTopic();
            }

            if (this.PrivateMessagesWindow != null)
            {
                UISettingsData uiSettingsData = this.UISettingsRepository.ReadSettings();
                this.PrivateMessagesWindow.SetFontSize(uiSettingsData.PrivateMessageFontSize);
            }
        }

        /// <summary>
        /// Show state of the last operation
        /// </summary>
        /// <param name="sender">the event sender</param>
        /// <param name="e">the vent params</param>
        internal void EngineOperationCompleted(object sender, EventArgs<OperationResult> e)
        {
            this.OperationCompletedCommand.Execute(e.Value);
        }

        #endregion

        #region private

        /// <summary>
        /// Attach to a chat engine
        /// </summary>
        /// <param name="newChatEngine">the chat engine to use</param>
        private void AttachChatEngine(IChatEngine newChatEngine)
        {
            this.chatEngine = newChatEngine;

            // Attach to the new chat engine
            if (this.chatEngine != null)
            {
                this.chatEngine.OperationCompleted += this.EngineOperationCompleted;
                this.chatEngine.TopicChanged += this.EngineTopicChanged;
                this.chatEngine.PublicMessagesReceived += this.EnginePublicMessagesReceived;
                this.chatEngine.UpdatedVersionReceived += this.EngineUpdatedVersionReceived;

                this.IsLoggedOn = this.chatEngine.IsLoggedOn;
            }
            else
            {
                this.IsLoggedOn = false;
            }
        }

        /// <summary>
        /// An updated version was received
        /// </summary>
        /// <param name="sender">the event sender</param>
        /// <param name="e">the vent params</param>
        private void EngineUpdatedVersionReceived(object sender, EventArgs<ClientVersion> e)
        {
            // get onto the right thread
            Application.Current.Dispatcher.Invoke(
                DispatcherPriority.Normal,
                new ClientVersionHandler(this.ReportNewerVersionAvailable),
                e.Value);
        }

        /// <summary>
        /// public messages received
        /// </summary>
        /// <param name="sender">the event sender</param>
        /// <param name="e">the vent params</param>
        private void EnginePublicMessagesReceived(object sender, EventArgs e)
        {
            // get onto the right thread
            this.Window.Dispatcher.Invoke(
                DispatcherPriority.Normal,
                new NoArgEventHandler(this.DisplayMessages));
        }

        /// <summary>
        /// Topic changed
        /// </summary>
        /// <param name="sender">the event sender</param>
        /// <param name="e">the vent params</param>
        private void EngineTopicChanged(object sender, EventArgs e)
        {
            // get onto the right thread
            Application.Current.Dispatcher.Invoke(
                DispatcherPriority.Normal,
                new NoArgEventHandler(this.DisplayTopic));
        }

        /// <summary>
        /// Show to the user the new version available
        /// </summary>
        /// <param name="clientVersion">the client version to show</param>
        private void ReportNewerVersionAvailable(ClientVersion clientVersion)
        {
            this.ShowNewVersionCommand.Execute(clientVersion);
        }

        /// <summary>
        /// Implements the IDisposable pattern
        /// </summary>
        /// <param name="disposing">true if called via the IDisposable Interface</param>
        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                // if disposing, dispose all managed and unmanaged resources.
                if (disposing)
                {
                    this.notificationSoundPlayer.Dispose();
                    this.notificationSoundPlayer = null;
                }
            }

            this.disposed = true;
        }

        #endregion
    }
}
